/*
 * Copyright (C) 2004-2006 the xine-project
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
 * USA
 *
 * $Id: player.c,v 1.86 2006/04/08 21:34:50 dsalt Exp $
 *
 * player object
 */

#include "globals.h"

#include <pthread.h>
#include <xine.h>
#include <string.h>
#include <stdlib.h>
#include <gtk/gtkradiomenuitem.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include "globals.h"
#include "menu.h"
#include "ui.h"
#include "utils.h"
#include "preferences.h"
#include "playlist.h"
#include "gtkvideo.h"
#include "snapshot.h"
#include "engine.h"
#include "player.h"

#define GXINE_VO_ZOOM_MIN (XINE_VO_ZOOM_MIN < 0 ? 0 : XINE_VO_ZOOM_MIN)
#define GXINE_MAX_SPU_CHANNEL (31)

xine_stream_t *stream; /* global stream */
xine_audio_port_t *audio_port;
xine_video_port_t *video_port;

/*
 * async play machanism
 *
 * always start xine engine from a seperate thread so the gui stays responsive
 */

static int             goto_pos, goto_time;
static pthread_t       play_thread;
static char           *cur_mrl = NULL, *cur_title = NULL;

static __attribute__ ((noreturn)) void *play_exec (void *data)
{
  xine_cfg_entry_t entry;
  char *hash, *mrl = NULL;

  /*pthread_mutex_lock (&engine_lock);*/
  pthread_mutex_lock (&widgets_update_lock);
  gdk_threads_enter();

  gxineinfo_clear (infobars);
  gxineinfo_update_line (infobars, 0, "%s", _("opening..."));
  gxineinfo_update_line (infobars, 1, "%s", cur_mrl);

  if ((!xine_config_lookup_entry (xine, "gui.subtitle_autoload", &entry)
       || entry.num_value)
      && !strncmp (cur_mrl, "file:", 5)
      && ((hash = strchr (cur_mrl, '#')) == NULL
	  || (strncmp (hash, "#subtitle:", 10)
	      && !strstr (hash, ";subtitle:"))))
  {
    char *name = cur_mrl + 5;
    if (name)
    {
      unsigned int length = (strchr (name, '#') ? : name + strlen (name)) - name;
      char file[length + 5]; /* allow for ".ext" + NUL */

      unsigned int i = UINT_MAX, ext = 0;

      /* URL decode (can't simply use g_filename_from_uri()...) */
      while (++i < length)
      {
	if (name[i] == '%')
	{
	  char c[3] = { name[i+1], name[i+2], 0 };
	  i += 2;
	  file[ext] = (char) strtol (c, NULL, 16);
	}
	else
	  file[ext] = name[i];
	++ext;
      }

      /* find last '.' in leaf */
      i = ext;
      if (ext)
	while (--ext && file[ext] != '.' && file[ext] != '/')
	  /**/;
      if (file[ext] == '/') /* if no '.' */
	ext = i;

      name = strrchr (name, '.');

      static const char subs[][4] = { "sub", "srt", "asc", "smi", "ssa", "txt" };
      for (i = 0; i < G_N_ELEMENTS (subs); ++i)
	if (!name || strcmp (name + 1, subs[i]))
	{
	  struct stat st;
	  sprintf (file + ext, ".%s", subs[i]);
	  if (stat (file, &st) > -1 && S_ISREG (st.st_mode))
	    break;
	}

      if (i < G_N_ELEMENTS (subs))
      {
	char *tmp = g_filename_to_uri (file, NULL, NULL);
	char *uri = g_filename_to_uri (tmp + 7, NULL, NULL);
	mrl = g_strconcat (cur_mrl, hash ? ";subtitle:" : "#subtitle:", uri + 7, NULL);
	free (uri);
	free (tmp);
      }
    }
  }

  gdk_threads_leave ();
  if (!xine_open (stream, mrl ? : cur_mrl))
  {
    int error;
    gdk_threads_enter ();
    switch (error = xine_get_error (stream))
    {
    case XINE_ERROR_NO_INPUT_PLUGIN:
      display_error (FROM_XINE, _("The xine engine failed to start."),
		     _("No input plugin was found.\n"
		     "Maybe the file does not exist or cannot be accessed, "
		     "or there is an error in the URL."));
      break;

    case XINE_ERROR_NO_DEMUX_PLUGIN:
      display_error (FROM_XINE,
		     playlist_showing_logo ()
		       ? _("Whoops. You seem to have a broken xine-lib.")
		       : _("The xine engine failed to start."),
		     _("No demuxer found - stream format not recognised."));
      break;

    case XINE_ERROR_DEMUX_FAILED:
      display_error (FROM_XINE, _("The xine engine failed to start."),
		     _("Demuxing failed.\n"
		       "Maybe the stream is corrupt or of an unexpected type."));
      break;

    case XINE_ERROR_MALFORMED_MRL:
      display_error (FROM_XINE, _("The xine engine failed to start."),
		     _("The MRL is malformed."));
      break;

    case XINE_ERROR_INPUT_FAILED:
      display_error (FROM_XINE, _("The xine engine failed to start."),
		     _("The stream could not be opened."));
      break;


    default:
      display_error (FROM_XINE, _("The xine engine failed to start."),
		     _("Unknown error (code %d)"), error);
    }

    if (!playlist_showing_logo ())
      g_idle_add ((GSourceFunc)playlist_logo, NULL);
    goto done; /* will call gdk_threads_leave */
  }

  gdk_threads_enter ();
  ui_set_status (playlist_showing_logo () ? UI_STOP : UI_PLAY);
  infobar_show_metadata (infobars);
  gdk_threads_leave ();

  xine_play (stream, goto_pos, goto_time);
  goto_pos = 0;
  goto_time = 0;

  /* correct for possible stream changes */
  gdk_threads_enter ();
  ui_set_status (playlist_showing_logo () ? UI_STOP : UI_PLAY);
  infobar_show_metadata (infobars);
  playlist_check_set_title ();

done:
  free (mrl);
  gdk_threads_leave ();
  pthread_mutex_unlock (&widgets_update_lock);
  /*pthread_mutex_unlock (&engine_lock);*/
  pthread_exit (NULL);
}

void player_launch (const char *title, const char *mrl, int pos, int pos_time)
{
  int err;
  pthread_attr_t attr;

  goto_pos  = pos < 0 ? 0 : pos;
  goto_time = pos_time < 0 ? 0 : pos_time;
  free (cur_title);
  free (cur_mrl);
  cur_title = title && !playlist_showing_logo () ? strdup (title) : NULL;
  cur_mrl = strdup (mrl);

  gtk_video_unblank_screen ((GtkVideo *)gtv);

  /* start separate thread, so xine_open will not block ui */
  pthread_attr_init (&attr);
  pthread_attr_setstacksize (&attr, 256 * 1024); /* probably overkill */
  if ((err = pthread_create (&play_thread, &attr, play_exec, NULL)) != 0)
  {
    display_error_modal (FROM_GXINE, _("Player failure"),
			 _("Can't create new thread: %s\n"), strerror(err));
    abort();
  }

  if (!playlist_showing_logo ())
    pthread_detach (play_thread);

  pthread_attr_destroy (&attr);
}

void player_wait (void)
{
  pthread_join (play_thread, NULL);
}

static void set_mute (int mute)
{
  ui_set_status (mute ? UI_AUDIO_MUTE : UI_AUDIO_UNMUTE);
  xine_set_param (stream, XINE_PARAM_AUDIO_MUTE, mute);
}

static inline int calc_speed (int i)
{
  return i ? (1 << (i - 1)) : 0;
}

static void set_speed (int speed)
{
  xine_set_param (stream, XINE_PARAM_SPEED, speed);
  if (speed == XINE_SPEED_NORMAL)
    ui_set_status (UI_PLAY);
  else if (speed > XINE_SPEED_NORMAL)
    ui_set_status (UI_FAST_FORWARD);
  else if (speed)
    ui_set_status (UI_PLAY_SLOW);
  else
    ui_set_status (UI_PAUSE);
}

static int listen_speed (void *se, se_t *se_t, se_o_t *o, se_prop_t *prop,
			 se_prop_read_t r)
{
  set_speed (calc_speed (r.i));
  return FALSE;
}

static void get_speed (se_prop_t *prop, se_prop_read_t *value)
{
  int speed = xine_get_param (stream, XINE_PARAM_SPEED);
  int bit = 0;
  if (speed)
    while (++bit, speed >>= 1);
  value->i = bit;
}

static void set_zoom (int zoom)
{
  if (zoom < GXINE_VO_ZOOM_MIN)
    zoom = GXINE_VO_ZOOM_MIN;
  else if (zoom > XINE_VO_ZOOM_MAX)
    zoom = XINE_VO_ZOOM_MAX;
  xine_set_param (stream, XINE_PARAM_VO_ZOOM_X, zoom);
  xine_set_param (stream, XINE_PARAM_VO_ZOOM_Y, zoom);
}

#define ACTION_SET(Func) \
  static void set_##Func (int v) \
  { \
    if (gtk_toggle_action_get_active (action_items.Func) != v) \
      gtk_action_activate (GTK_ACTION(action_items.Func)); \
  }

ACTION_SET(fullscreen)
static void get_fullscreen (se_prop_t *prop, se_prop_read_t *value)
{
  value->i = gtk_video_is_fullscreen ((GtkVideo *)gtv);
}

ACTION_SET(deinterlace)
static void get_deinterlace (se_prop_t *prop, se_prop_read_t *value)
{
  value->i = gtk_video_get_use_post_plugins_deinterlace ((GtkVideo *)gtv);
}

static void set_subtitles (int sub)
{
  if (sub >= -2 && sub <= GXINE_MAX_SPU_CHANNEL)
  {
    GSList *menuitem = action_items.subtitles;
    int count = g_slist_length (menuitem) - 3;
    if (count >= sub)
    {
      while (menuitem && --count > sub)
	menuitem = menuitem->next;
      if (menuitem)
      {
        gtk_action_activate (menuitem->data);
        return;
      }
    }
    /* not in menu */
    gtk_action_activate (g_slist_last (action_items.subtitles)->data);
    xine_set_param (stream, XINE_PARAM_SPU_CHANNEL, sub);
  }
}

static void set_aspect (int aspect)
{
  GSList *menuitem = action_items.aspect;
  int count = g_slist_length (menuitem);
  if (aspect >= XINE_VO_ASPECT_NUM_RATIOS)
    aspect = XINE_VO_ASPECT_AUTO;
  else if (aspect < 0)
    aspect = XINE_VO_ASPECT_NUM_RATIOS - 1;
  while (--count > aspect && menuitem->next)
    menuitem = menuitem->next;
  if (menuitem)
    gtk_action_activate (menuitem->data);
}

ACTION_SET(auto_resize)
static void get_auto_resize (se_prop_t *prop, se_prop_read_t *value)
{
  value->i = gtk_video_get_auto_resize ((GtkVideo *)gtv);
}

ACTION_SET(auto_rescale)
static void get_auto_rescale (se_prop_t *prop, se_prop_read_t *value)
{
  value->i = gtk_video_get_auto_rescale ((GtkVideo *)gtv);
}

ACTION_SET(vo_postproc)
static void get_vo_postproc (se_prop_t *prop, se_prop_read_t *value)
{
  value->i = gtk_video_get_use_post_plugins_video ((GtkVideo *)gtv);
}

ACTION_SET(ao_postproc)
static void get_ao_postproc (se_prop_t *prop, se_prop_read_t *value)
{
  value->i = gtk_video_get_use_post_plugins_audio ((GtkVideo *)gtv);
}

ACTION_SET(wm_unblank)
static void get_wm_unblank (se_prop_t *prop, se_prop_read_t *value)
{
  value->i = gtk_video_get_windowed_unblank ((GtkVideo *)gtv);
}

static void update_postproc_chain (const char *pref, const char *value)
{
  xine_cfg_entry_t entry;
  xine_config_lookup_entry (xine, pref, &entry);
  entry.str_value = (char *)value;
  preferences_update_entry (&entry);
}

static JSBool set_postproc_chain (const char *func, const char *pref,
				  JSContext *cx, JSObject *obj,
				  uintN argc, jsval *argv, jsval *rval)
{
  se_log_fncall (func);
  se_argc_check (1, func);
  se_arg_is_string (0, func);
  update_postproc_chain (pref,
			 JS_GetStringBytes (JS_ValueToString (cx, argv[0])));
  return JS_TRUE;
}


static JSBool set_vo_postproc_chain (JSContext *cx, JSObject *obj,
				     uintN argc, jsval *argv, jsval *rval)
{
  return set_postproc_chain ("vo_postproc.set_chain", "gui.post_plugins.video",
			     cx, obj, argc, argv, rval);
}

static JSBool set_ao_postproc_chain (JSContext *cx, JSObject *obj,
				     uintN argc, jsval *argv, jsval *rval)
{
  return set_postproc_chain ("ao_postproc.set_chain", "gui.post_plugins.audio",
			     cx, obj, argc, argv, rval);
}

static int listen_postproc_chain (void *pref, se_t *se, se_o_t *obj,
				  se_prop_t *prop, se_prop_read_t value)
{
  update_postproc_chain (pref, value.s);
  return 0;
}

static JSBool get_time (JSContext *cx, JSObject *obj, uintN argc,
		     jsval *argv, jsval *rval)
{
  int   pos, pos_time, len;
  se_log_fncall ("get_time");
  xine_get_pos_length (stream, &pos, &pos_time, &len);
  *rval = INT_TO_JSVAL (pos_time);
  return JS_TRUE;
}

#ifdef WITH_DEPRECATED

static JSBool js_get_speed (JSContext *cx, JSObject *obj, uintN argc,
			    jsval *argv, jsval *rval)
{
  se_prop_read_t speed;
  se_log_fncall_deprecated ("get_speed");
  get_speed (NULL, &speed);
  *rval = INT_TO_JSVAL (speed.i);
  return JS_TRUE;
}

static JSBool js_get_volume (JSContext *cx, JSObject *obj, uintN argc,
			    jsval *argv, jsval *rval)
{
  /* se_t *se = (se_t *) JS_GetContextPrivate(cx); */
  int   volume;
  se_log_fncall_deprecated ("get_volume");
  volume = xine_get_param (stream, XINE_PARAM_AUDIO_VOLUME);
  *rval = INT_TO_JSVAL (volume);
  return JS_TRUE;
}

static JSBool js_get_mute (JSContext *cx, JSObject *obj, uintN argc,
			   jsval *argv, jsval *rval)
{
  /* se_t *se = (se_t *) JS_GetContextPrivate(cx); */
  int mute;
  se_log_fncall_deprecated ("get_mute");
  mute = xine_get_param (stream, XINE_PARAM_AUDIO_MUTE);
  *rval = BOOLEAN_TO_JSVAL (mute);
  return JS_TRUE;
}

static JSBool js_get_zoom (JSContext *cx, JSObject *obj, uintN argc,
			   jsval *argv, jsval *rval)
{
  /* se_t *se = (se_t *) JS_GetContextPrivate(cx); */
  int   zoom;
  se_log_fncall_deprecated ("get_zoom");
  zoom = xine_get_param (stream, XINE_PARAM_VO_ZOOM_X);
  *rval = INT_TO_JSVAL (zoom);
  return JS_TRUE;
}

#endif /* WITH_DEPRECATED */

static JSBool controls_pause (JSContext *cx, JSObject *obj, uintN argc,
			      jsval *argv, jsval *rval)
{
  int32 pause_state=-1;

  se_log_fncall_checkinit ("pause");
  se_argc_check_max (1, "pause");

  if (argc == 1)
  {
    se_arg_is_int_or_bool (0, "pause");
    JS_ValueToInt32 (cx, argv[0], &pause_state);
  }

  switch (pause_state)
  {
  case 0: /* pause off */
    set_speed (XINE_SPEED_NORMAL);
    break;
  case 1: /* pause on */
    set_speed (XINE_SPEED_PAUSE);
    break;
  default: /* toggle */
    if (xine_get_param (stream, XINE_PARAM_SPEED) != XINE_SPEED_PAUSE)
      set_speed (XINE_SPEED_PAUSE);
    else
      set_speed (XINE_SPEED_NORMAL);
  }

  return JS_TRUE;
}

static JSBool controls_stop (JSContext *cx, JSObject *obj, uintN argc,
			     jsval *argv, jsval *rval)
{
  se_log_fncall_checkinit ("stop");
  xine_stop (stream);
  playlist_logo (NULL);
  ui_set_control_adjustment (Control_SEEKER, 0);
  return JS_TRUE;
}

static JSBool controls_eject (JSContext *cx, JSObject *obj, uintN argc,
			      jsval *argv, jsval *rval)
{
  se_log_fncall_checkinit ("eject");
  if (xine_get_status (stream) == XINE_STATUS_PLAY)
    xine_stop (stream);
  gdk_threads_leave (); /* need to do this to allow play_exec() to proceed */
  playlist_logo (cx); /* doesn't matter so long as it's not NULL */
  gdk_threads_enter ();
  ui_set_control_adjustment (Control_SEEKER, 0);
  int ret = xine_eject (stream);
  *rval = BOOLEAN_TO_JSVAL (ret);
  return JS_TRUE;
}

#ifdef WITH_DEPRECATED

static JSBool js_set_speed (JSContext *cx, JSObject *obj, uintN argc,
			    jsval *argv, jsval *rval)
{
  int32 speed;

  se_log_fncall_deprecated ("set_speed");

  se_argc_check (1, "set_speed");
  se_arg_is_int (0, "set_speed");

  JS_ValueToInt32 (cx, argv[0], &speed);

  if (speed<0)
    speed = 0;
  set_speed (calc_speed (speed));

  return JS_TRUE;
}

static JSBool js_set_volume (JSContext *cx, JSObject *obj, uintN argc,
			    jsval *argv, jsval *rval)
{
  int32 volume;

  se_log_fncall_deprecated ("set_volume");
  se_argc_check (1, "set_volume");
  se_arg_is_int (0, "set_volume");

  JS_ValueToInt32 (cx, argv[0], &volume);

  if (volume<0)
    volume = 0;
  ui_set_control_adjustment (Control_VOLUME, volume);
  ui_xine_set_param_from_adjustment (Control_VOLUME);

  return JS_TRUE;
}

static JSBool js_set_mute (JSContext *cx, JSObject *obj, uintN argc,
			   jsval *argv, jsval *rval)
{
  JSBool mute;

  se_log_fncall_deprecated ("set_mute");
  se_argc_check_max (1, "set_mute");

  if (argc == 1)
  {
    se_arg_is_int_or_bool (0, "set_mute");
    JS_ValueToBoolean (cx, argv[0], &mute);
  }
  else
    mute = !xine_get_param (stream, XINE_PARAM_AUDIO_MUTE);

  set_mute (mute);

  return JS_TRUE;
}

static JSBool js_set_zoom (JSContext *cx, JSObject *obj, uintN argc,
			   jsval *argv, jsval *rval)
{
  int32 zoom;

  se_log_fncall_deprecated ("set_zoom");
  se_argc_check (1, "set_zoom");
  se_arg_is_int (0, "set_zoom");

  JS_ValueToInt32 (cx, argv[0], &zoom);
  set_zoom (zoom);

  return JS_TRUE;
}

#endif /* WITH_DEPRECATED */

static int zoomcb, zoomcb_id = 0;

static gboolean video_rescale_cb (gpointer unused)
{
  gtk_video_rescale ((GtkVideo *) gtv, zoomcb);
  zoomcb_id = 0;
  return FALSE;
}

static gboolean set_video_size_cb (gpointer unused)
{
  gdk_threads_enter ();
  /* find & activate an action item which corresponds to the requested scale */
  GtkAction *action = (zoomcb == -1) ? NULL : find_video_size_action (zoomcb);
  if (action)
    gtk_action_activate (action);
  else
  {
    scale_changed_cb (NULL, zoomcb, NULL);
    zoomcb_id = g_idle_add ((GSourceFunc) video_rescale_cb, NULL);
  }
  gdk_threads_leave ();
  return FALSE;
}

static JSBool js_set_video_size (JSContext *cx, JSObject *obj, uintN argc,
			   jsval *argv, jsval *rval)
{
  se_log_fncall ("set_video_size");
  se_argc_check (1, "set_video_size");
  se_arg_is_int (0, "set_video_size");

  int32 zoom;
  JS_ValueToInt32 (cx, argv[0], &zoom);

  if (zoom < GXINE_VO_ZOOM_MIN && zoom != -1)
    zoom = GXINE_VO_ZOOM_MIN;
  else if (zoom > XINE_VO_ZOOM_MAX)
    zoom = XINE_VO_ZOOM_MAX;

  zoomcb = zoom;
  if (zoomcb_id)
    g_source_remove (zoomcb_id);
  zoomcb_id = g_idle_add ((GSourceFunc) set_video_size_cb, NULL);

  return JS_TRUE;
}

#ifdef WITH_DEPRECATED

static JSBool js_set_fullscreen (JSContext *cx, JSObject *obj, uintN argc,
				 jsval *argv, jsval *rval)
{
  int32 fs;

  se_log_fncall_deprecated ("set_fullscreen");
  se_argc_check_max (1, "set_fullscreen");

  if (argc == 1)
  {
    se_arg_is_int_or_bool (0, "set_fullscreen");
    JS_ValueToInt32 (cx, argv[0], &fs);
  }
  else
    fs = !gtk_video_is_fullscreen ((GtkVideo *)gtv);

  set_fullscreen (fs);
  return JS_TRUE;
}

static JSBool js_set_deinterlace (JSContext *cx, JSObject *obj, uintN argc,
				 jsval *argv, jsval *rval)
{
  int32 di;

  se_log_fncall_deprecated ("set_deinterlace");
  se_argc_check_max (1, "set_deinterlace");

  if (argc == 1)
  {
    se_arg_is_int_or_bool (0, "set_deinterlace");
    JS_ValueToInt32 (cx, argv[0], &di);
  }
  else
    di = !gtk_video_get_use_post_plugins_deinterlace ((GtkVideo *)gtv);

  set_deinterlace (di);
  return JS_TRUE;
}

static JSBool js_set_postproc_video (JSContext *cx, JSObject *obj,
				     uintN argc, jsval *argv, jsval *rval)
{
  se_t *se = (se_t *) JS_GetContextPrivate(cx);
  int32 di;

  se_log_fncall_deprecated ("set_postproc_video");
  se_argc_check_max (1, "set_postproc_video");

  if (argc == 1)
  {
    if (JSVAL_IS_INT (argv[0]))
      JS_ValueToInt32 (cx, argv[0], &di);
    else if (JSVAL_IS_STRING (argv[0]))
      return set_vo_postproc_chain (cx, obj, argc, argv, rval);
    else
    {
      se->print_cb (se->print_cb_data,
		   _("error: %s() argument %d is neither int nor string\n"),
		   "set_postproc_video", 1);
      return JS_TRUE;
    }
  } else
    di = !gtk_video_get_use_post_plugins_video ((GtkVideo *)gtv);

  set_vo_postproc (di);
  return JS_TRUE;
}

static JSBool js_set_postproc_audio (JSContext *cx, JSObject *obj,
				     uintN argc, jsval *argv, jsval *rval)
{
  se_t *se = (se_t *) JS_GetContextPrivate(cx);
  int32 di;

  se_log_fncall_deprecated ("set_postproc_audio");
  se_argc_check_max (1, "set_postproc_audio");

  if (argc == 1)
  {
    if (JSVAL_IS_INT (argv[0]))
      JS_ValueToInt32 (cx, argv[0], &di);
    else if (JSVAL_IS_STRING (argv[0]))
      return set_ao_postproc_chain (cx, obj, argc, argv, rval);
    else
    {
      se->print_cb (se->print_cb_data,
		   _("error: %s() argument %d is neither int nor string\n"),
		   "set_postproc_audio", 1);
      return JS_TRUE;
    }
  } else
    di = !gtk_video_get_use_post_plugins_audio ((GtkVideo *)gtv);

  set_ao_postproc (di);
  return JS_TRUE;
}

static JSBool js_set_auto_resize (JSContext *cx, JSObject *obj, uintN argc,
				 jsval *argv, jsval *rval)
{
  int32 ar;

  se_log_fncall_deprecated ("set_auto_resize");
  se_argc_check_max (1, "set_auto_resize");

  if (argc == 1)
  {
    se_arg_is_int_or_bool (0, "set_auto_resize");
    JS_ValueToInt32 (cx, argv[0], &ar);
  }
  else
    ar = !gtk_video_get_auto_resize ((GtkVideo *)gtv);

  set_auto_resize (ar);
  return JS_TRUE;
}

static JSBool js_set_auto_rescale (JSContext *cx, JSObject *obj, uintN argc,
				   jsval *argv, jsval *rval)
{
  int32 ar;

  se_log_fncall_deprecated ("set_auto_rescale");
  se_argc_check_max (1, "set_auto_rescale");

  if (argc == 1)
  {
    se_arg_is_int_or_bool (0, "set_auto_rescale");
    JS_ValueToInt32 (cx, argv[0], &ar);
  }
  else
    ar = !gtk_video_get_auto_rescale ((GtkVideo *)gtv);

  set_auto_rescale (ar);
  return JS_TRUE;
}

static JSBool js_set_aspect (JSContext *cx, JSObject *obj, uintN argc,
			     jsval *argv, jsval *rval)
{
  int32 aspect;

  se_log_fncall_deprecated ("set_aspect");
  se_argc_check_max (1, "set_aspect");

  if (argc==1)
  {
    se_arg_is_int (0, "set_aspect");
    JS_ValueToInt32 (cx, argv[0], &aspect);
  }
  else
    aspect = xine_get_param (stream, XINE_PARAM_VO_ASPECT_RATIO) + 1;

  set_aspect (aspect);
  return JS_TRUE;
}

#endif /* WITH_DEPRECATED */

static JSBool js_snapshot (JSContext *cx, JSObject *obj, uintN argc,
			   jsval *argv, jsval *rval)
{
  JSString *str;
  char     *fname = NULL;
  int32	    scale = -1, blend = -1;

  se_log_fncall_checkinit ("snapshot");
  se_argc_check_range (0, 3, "snapshot");

  switch (argc) /* note fall-through */
  {
  case 3:
    se_arg_is_int (2, "snapshot");
    JS_ValueToInt32 (cx, argv[2], &blend);
  case 2:
    se_arg_is_int (1, "snapshot");
    JS_ValueToInt32 (cx, argv[1], &scale);
  case 1:
    se_arg_is_string (0, "snapshot");
    str = JS_ValueToString (cx, argv[0]);
    fname = JS_GetStringBytes (str);
  }

  make_snapshot (fname, scale, blend);

  return JS_TRUE;
}


static JSBool controls_play (JSContext *cx, JSObject *obj, uintN argc,
			     jsval *argv, jsval *rval)
{
  se_log_fncall_checkinit ("play");
  se_argc_check_max (3, "play");

  char *mrl = NULL;
  int32 pos = -1, pos_time = -1;

  switch (argc)
  {
  case 0:
    break;

  case 1:
    {
      JSString *str;
      se_arg_is_string (0, "play");

      str = JS_ValueToString (cx, argv[0]);

      mrl = JS_GetStringBytes (str);
      se_log ("playing '%s' from start\n", mrl);
    }
    break;

  case 2:
    {
      se_arg_is_int (0, "play");
      se_arg_is_int (1, "play");

      JS_ValueToInt32 (cx, argv[0], &pos);
      JS_ValueToInt32 (cx, argv[1], &pos_time);
      mrl = NULL;
      se_log ("playing from %d, %d\n", pos, pos_time);
    }
    break;

  case 3:
    {
      JSString *str;
      se_arg_is_string (0, "play");
      se_arg_is_int (1, "play");
      se_arg_is_int (2, "play");

      str = JS_ValueToString (cx, argv[0]);
      JS_ValueToInt32 (cx, argv[1], &pos);
      JS_ValueToInt32 (cx, argv[2], &pos_time);
      mrl = JS_GetStringBytes (str);
      se_log ("playing '%s' from %d, %d\n", mrl, pos, pos_time);
    }
    break;
  }

  if (pos > 0)
    pos *= 655;

  if (mrl)
    playlist_play_from (playlist_add_mrl (mrl, -1), pos, pos_time);
  else
  {
    play_item_t *item = playlist_get_current_item ();

    if (item)
    {
      switch (xine_get_status (stream))
      {
      case XINE_STATUS_STOP:
      case XINE_STATUS_IDLE:
	playlist_play_from (playlist_get_list_pos (), pos,
			   pos_time < 0 ? item->start_time : pos_time);
	break;
      default:
	if (pos >= 0 || pos_time >= 0)
	{
	  gtk_video_unblank_screen ((GtkVideo *)gtv);
	  xine_play (stream, pos < 0 ? 0 : pos, pos_time < 0 ? 0 : pos_time);
        }
	break;
      }
      play_item_dispose (item);
    } else
      logprintf (_("script_engine: error: no valid play item available\n"));
  }

  if (xine_get_param (stream, XINE_PARAM_SPEED) != XINE_SPEED_NORMAL)
    set_speed (XINE_SPEED_NORMAL);

  return JS_TRUE;
}

/* Crude hack to allow combined play/pause function.
 * (Useful on some keyboards with CD/DVD control keys.)
 */
static JSBool controls_play_pause (JSContext *cx, JSObject *obj, uintN argc,
                                  jsval *argv, jsval *rval)
{
  se_log_fncall_checkinit ("play_pause");
  se_argc_check_max (1, "play_pause");

  if (argc == 1)
    se_arg_is_int_or_bool (0, "play_pause");

  switch (xine_get_status (stream))
  {
  case XINE_STATUS_STOP:
  case XINE_STATUS_IDLE:
    return controls_play (cx, obj, 0, 0, rval);
  default:
    return controls_pause (cx, obj, argc, argv, rval);
  }
}

static JSBool js_is_live_stream (JSContext *cx, JSObject *obj, uintN argc,
                                 jsval *argv, jsval *rval)
{
  *rval = JSVAL_FALSE;

  se_log_fncall ("is_live_stream");
  se_argc_check_max (0, "is_live_stream");

  if (player_live_stream ())
    *rval = JSVAL_TRUE;

  return JS_TRUE;
}

int player_live_stream (void)
{
  gint pos_stream, pos_time, length_time;

  return (xine_get_status (stream) == XINE_STATUS_PLAY) &&
	 (xine_get_pos_length (stream, &pos_stream, &pos_time, &length_time)) &&
	 length_time == 0 &&
	 !xine_get_stream_info (stream, XINE_STREAM_INFO_SEEKABLE);
}

const char *player_get_cur_title (void)
{
  return cur_title;
}

const char *player_get_cur_mrl (void)
{
  return cur_mrl;
}


static xine_audio_port_t *load_audio_out_driver (void)
{
  xine_audio_port_t      *audio_port;

  if (!audio_driver_id)
  {
    char **driver_ids = (char **) xine_list_audio_output_plugins (xine);
    char **choices = malloc (sizeof (char *) * 100);
    choices[0] = "auto";

    int i = -1;
    while (driver_ids[++i])
      choices[i + 1] = strdup(driver_ids[i]);
    choices[i + 1]=0;

    /* try to init audio with stored information */
    i = xine_config_register_enum (xine,
                                   "audio.driver", 0,
                                   choices,
                                   _("audio driver to use"),
                                   NULL, 10, gtk_true, NULL);
    audio_driver_id = choices[i];
  }

  if (!strcmp (audio_driver_id, "null"))
    return NULL;

  if (strcmp (audio_driver_id, "auto"))
  {
    audio_port = xine_open_audio_driver (xine, audio_driver_id, NULL);
    if (audio_port)
      return audio_port;
    else
      g_printerr (_("audio driver %s failed\n"), audio_driver_id);
  }

  /* autoprobe */
  return xine_open_audio_driver (xine, NULL, NULL);
}

/* Properties */

void player_init (void)
{
/*
  se_o_t            *controls_obj;
 */

  cur_mrl = NULL;

  /* load drivers, create xine stream */

  audio_port = load_audio_out_driver ();
  video_port = xine_open_video_driver (xine, "none", 0, NULL);

  stream = xine_stream_new (xine, audio_port, video_port);

  /*
   * set up script engine objects and functions
   */

/*
  controls_obj = se_create_object (gse, NULL, "controls", NULL);

  se_defun (gse, controls_obj, "play", controls_play, 0, 0,
	    SE_GROUP_HIDDEN, NULL, NULL);
 */

  {
    static const ui_property_t int_props[] = {
      /* Text is help for the commands. Format is:
       *   To the left of the semicolon, a comma-separated list of
       *     VARIABLE[=TYPE] or METHOD([PARAMETER TYPE,...])
       *   To the right, descriptive text
       */
      { "av_speed", N_("v=int, min, max; playback speed"), 0, get_speed, set_speed, XINE_SPEED_PAUSE, XINE_SPEED_FAST_4, FALSE, { listen_speed } },
      { "vo_aspect", N_("v=int; aspect ratio"), XINE_PARAM_VO_ASPECT_RATIO, NULL, set_aspect, 0, XINE_VO_ASPECT_NUM_RATIOS - 1, TRUE },
      { "vo_subtitle", N_("v=int, min, max; subtitle channel"), XINE_PARAM_SPU_CHANNEL, NULL, set_subtitles, -2, GXINE_MAX_SPU_CHANNEL },
      { "vo_zoom", N_("v=int, min, max; video zoom (within window)"), XINE_PARAM_VO_ZOOM_X, NULL, set_zoom, XINE_VO_ZOOM_MIN, XINE_VO_ZOOM_MAX },
      { NULL }
    };
    static const ui_property_t bool_props[] = {
      { "ao_mute", N_("v=bool, toggle(): audio mute"), XINE_PARAM_AUDIO_MUTE, NULL, set_mute },
      { "vo_auto_resize", N_("v=bool, toggle(): auto-resize video on frame shape change"), 0, get_auto_resize, set_auto_resize },
      { "vo_auto_rescale", N_("v=bool, toggle(): double size of low-resolution video"), 0, get_auto_rescale, set_auto_rescale },
      { "vo_fullscreen", N_("v=bool, toggle(): full-screen mode"), 0, get_fullscreen, set_fullscreen },
      { "vo_deinterlace", N_("v=bool, toggle(): enable deinterlacing"), 0, get_deinterlace, set_deinterlace },
      { "ao_postproc", N_("v=bool, toggle(): audio post-processing; chain=string: plugin chain"), 0, get_ao_postproc, set_ao_postproc },
      { "vo_postproc", N_("v=bool, toggle(): video post-processing; chain=string: plugin chain"), 0, get_vo_postproc, set_vo_postproc },
      { "wm_unblank", N_("v=bool, toggle(): unblanking in windowed mode"), 0, get_wm_unblank, set_wm_unblank },
      { NULL }
    };
    static const ui_property_t zoom_subprops[] = {
      { "x", N_("v=int, min, max; video zoom (within window)"), XINE_PARAM_VO_ZOOM_X, NULL, NULL, XINE_VO_ZOOM_MIN, XINE_VO_ZOOM_MAX },
      { "y", N_("v=int, min, max; video zoom (within window)"), XINE_PARAM_VO_ZOOM_Y, NULL, NULL, XINE_VO_ZOOM_MIN, XINE_VO_ZOOM_MAX },
      { NULL }
    };

    ui_create_properties (int_props, NULL, SE_TYPE_INT);
    ui_create_properties (bool_props, NULL, SE_TYPE_BOOL);
    ui_create_properties (zoom_subprops, se_find_object (gse, NULL, "vo_zoom"),
			  SE_TYPE_INT);
  }

  /* a few extras - chain definition should be done via the GUI */
  se_o_t *obj = se_find_object (gse, NULL, "ao_postproc");
  se_defun (gse, obj, "set_chain",
	    set_ao_postproc_chain, 0, 0, SE_GROUP_HIDDEN, NULL, NULL);
  se_prop_create_xine_id (gse, obj, "chain", "gui.post_plugins.audio");
  se_prop_add_listener (gse, obj, "chain",
			listen_postproc_chain, "gui.post_plugins.audio");

  obj = se_find_object (gse, NULL, "vo_postproc");
  se_defun (gse, obj, "set_chain",
	    set_vo_postproc_chain, 0, 0, SE_GROUP_HIDDEN, NULL, NULL);
  se_prop_create_xine_id (gse, obj, "chain", "gui.post_plugins.video");
  se_prop_add_listener (gse, obj, "chain",
			listen_postproc_chain, "gui.post_plugins.video");

  /*
   * hook up global xine functions for convenience
   */

  {
    static const se_f_def_t defs[] = {
      { "play", controls_play, 0, 0,
	/* help text describes function parameters */
	SE_GROUP_ENGINE, N_("[mrl] [, pos, time]"),
	N_("time in milliseconds") },
      { "get_time", get_time, 0, 0,
	SE_GROUP_ENGINE, NULL, NULL },
      { "pause", controls_pause, 0, 0,
  	SE_GROUP_ENGINE, N_("[bool]"), NULL },
      { "stop", controls_stop, 0, 0,
	SE_GROUP_ENGINE, NULL, NULL },
      { "eject", controls_eject, 0, 0,
	SE_GROUP_ENGINE, NULL, NULL },
      { "play_pause", controls_play_pause, 0, 0,
	SE_GROUP_ENGINE, N_("[bool]"), NULL },
      { "snapshot", js_snapshot, 0, 0,
        /* these correspond to the options in the snapshot-save dialogue box */
	SE_GROUP_ENGINE, N_("[file name [, scale, blend]]"), NULL },

      { "set_video_size", js_set_video_size, 0, 0,
	SE_GROUP_PROPERTIES, N_("int"), NULL },

#ifdef WITH_DEPRECATED
      { "set_speed", js_set_speed, 0, 0,
	SE_GROUP_HIDDEN, NULL, NULL },
      { "get_speed", js_get_speed, 0, 0,
	SE_GROUP_HIDDEN, NULL, NULL },
      { "set_volume", js_set_volume, 0, 0,
	SE_GROUP_HIDDEN, NULL, NULL },
      { "get_volume", js_get_volume, 0, 0,
	SE_GROUP_HIDDEN, NULL, NULL },
      { "set_mute", js_set_mute, 0, 0,
	SE_GROUP_HIDDEN, NULL, NULL },
      { "get_mute", js_get_mute, 0, 0,
	SE_GROUP_HIDDEN, NULL, NULL },
      { "set_zoom", js_set_zoom, 0, 0,
	SE_GROUP_HIDDEN, NULL, NULL },
      { "get_zoom", js_get_zoom, 0, 0,
	SE_GROUP_HIDDEN, NULL, NULL },
      { "set_fullscreen", js_set_fullscreen, 0, 0,
	SE_GROUP_HIDDEN, NULL, NULL },
      { "set_aspect", js_set_aspect, 0, 0,
	SE_GROUP_HIDDEN, NULL, NULL },
      { "set_deinterlace", js_set_deinterlace, 0, 0,
	SE_GROUP_HIDDEN, NULL, NULL },
      { "set_postproc_video", js_set_postproc_video, 0, 0,
	SE_GROUP_HIDDEN, NULL, NULL },
      { "set_postproc_audio", js_set_postproc_audio, 0, 0,
	SE_GROUP_HIDDEN, NULL, NULL },
      { "set_auto_resize", js_set_auto_resize, 0, 0,
	SE_GROUP_HIDDEN, NULL, NULL },
      { "set_auto_rescale", js_set_auto_rescale, 0, 0,
	SE_GROUP_HIDDEN, NULL, NULL },
#endif /* WITH_DEPRECATED */

      { "is_live_stream", js_is_live_stream, 0, 0,
	SE_GROUP_PROPERTIES, NULL, NULL },
      { NULL }
    };
    se_defuns (gse, NULL, defs);
  }

  {
    static char *window_size_labels[] = {
      N_("50%"), N_("75%"), N_("100%"), N_("150%"), N_("200%"),
      NULL
    };
    xine_config_register_enum
      (xine, "gui.window_size", 0, window_size_labels,
       _("The main window's default size"),
       NULL, 0, NULL, NULL);
    xine_config_register_bool
      (xine, "gui.magnify_lowres_video", 0,
       _("Double the size of small video streams"),
       _("This affects video streams whose dimensions are at most ⅓ "
	 "of those of the display."),
       0, gtk_true, NULL);
    xine_config_register_bool
      (xine, "gui.subtitle_autoload", 1,
       _("Subtitle autoloading"),
       _("Automatically load subtitles if they exist."),
       0, gtk_true, NULL);
  }
}
